package com.zhixinglu.spring.boot.generator.nickname.utils;

import org.redisson.Redisson;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.StringCodec;

import java.util.Random;

/**
 * <p>
 * NicknameBloomFilterGenerator
 * </p>
 *
 * @author weixin
 * @date 2023/3/27 15:42
 */
public class NicknameBloomFilterGenerator {

    private static final int NAME_LENGTH = 3;
    private static final int MAX_DIGIT = 999;
    private static final int MAX_RETRY = 10;

    private final RedissonClient redissonClient;

    private final Random random = new Random();
    private final RAtomicLong counter;
    private final RBloomFilter<String> filter;
    private final int maxDigit;
    private final int nameLength;

    public NicknameBloomFilterGenerator(RedissonClient redissonClient) {
        this(redissonClient, NAME_LENGTH, MAX_DIGIT);
    }

    public NicknameBloomFilterGenerator(RedissonClient redissonClient, int nameLength, int maxDigit) {
        this.redissonClient = redissonClient;
        this.nameLength = nameLength;
        this.maxDigit = maxDigit;
        this.counter = redissonClient.getAtomicLong("nickname_counter");
        this.filter = redissonClient.getBloomFilter("nickname_filter", new StringCodec());
        this.filter.tryInit(17576000L, 0.03);
    }

    public String generate() {
        int retry = 0;
        while (retry < MAX_RETRY) {
            String name = generateName();
            String num = generateNumber();
            String nickname = name + num;
            if (!filter.contains(nickname)) {
                if (filter.add(nickname)) {
                    return nickname;
                }
            }
            retry++;
        }
        throw new RuntimeException("Failed to generate unique nickname.");
    }

    private String generateName() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < NAME_LENGTH; i++) {
            char c = (char) (random.nextInt(26) + 'a');
            sb.append(c);
        }
        return sb.toString();
    }

    private String generateNumber() {
        long current = counter.getAndIncrement();
        long num = current % maxDigit;
        if (num == 0 && current != 0) {
            counter.decrementAndGet();
            return generateNumber();
        } else {
            return String.format("%03d", num);
        }
    }

}
